Example Code
The following C# code samples show how to create a dotnet class to interact with the controller's API, then use it to log in, query the controller's serial number, and log out.
Create the Dotnet Class
Copy
using System.Collections.Specialized;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace ICT
{
public class ControllerAPI
{
private byte[] SessionAesKey;
private readonly string HostAddress;
private readonly bool HostHttps;
private readonly HttpClient client;
// Construct a new object with the provided parameters
public ControllerAPI(string Host, bool Https)
{
// Keep a copy of the IP address or hostname
HostAddress = Host;
// Remember the HTTPS/HTTP preference
HostHttps = Https;
// Create an empty session key for AES over HTTP
SessionAesKey = new byte[16];
// Workaround to accept all self-signed certificates. We do not recommend using this in production code
HttpClientHandler httpClientHandler = new()
{
ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
{
// Warning: Accept any certificate
return true;
}
};
// Create an HTTP client
client = new HttpClient(httpClientHandler);
}
// Function to determine if we need to encrypt the outgoing request parameters
private bool ShouldEncrypt(string parameters)
{
if (HostHttps)
{
// HTTPS is protected already and no additional encryption is required
return false;
}
// For initial session interaction, no encryption is possible as we do not yet have a session key
if (parameters.StartsWith("Command&Type=Session&SubType=InitSession") ||
parameters.StartsWith("Command&Type=Session&SubType=CheckPassword"))
{
return false;
}
// Everything else can be encrypted
return true;
}
// Function to perform a POST request and read the response as a string
private async Task<string> GetResponseString(string parameters)
{
// Determine if we need to encrypt the payloads
bool shouldEncrypt = ShouldEncrypt(parameters);
if (shouldEncrypt)
{
parameters = Encrypt(parameters);
}
// Set the correct protocol
string protocol = HostHttps ? "https" : "http";
// Send a POST request with the parameters
HttpResponseMessage response = await client.PostAsync($"{protocol}://{HostAddress}/PRT_CTRL_DIN_ISAPI.dll", new StringContent(parameters));
if (response.IsSuccessStatusCode == false)
{
return string.Empty;
}
// Read the response as a string
string responseString = await response.Content.ReadAsStringAsync();
// Check if we need to decrypt the response
if (shouldEncrypt)
{
responseString = Decrypt(responseString);
}
return responseString;
}
// Function to calculate a SHA1 checksum from a string
public static async Task<string> Sha1FromString(string sourceString)
{
// Create a SHA1 hash object
using SHA1 sha1 = SHA1.Create();
// Convert the source string to UTF8 bytes
byte[] bytes = Encoding.UTF8.GetBytes(sourceString);
// Create a memory stream so we can calculate the SHA1 asynchronously
using MemoryStream stream = new(bytes);
// Calculate SHA1 checksum of byte stream
byte[] sourceStringHash = await sha1.ComputeHashAsync(stream);
// Return an uppercase hexadecimal string of the SHA1 checksum
return BitConverter.ToString(sourceStringHash).Replace("-", "").ToUpper();
}
// Function to calculate the XOR between a string and numeric value
private static string XorFn(string inputString, UInt32 number)
{
// Convert the numeric value to a binary string and make sure it has the correct padding at the start
string numberBinary = Convert.ToString(number, 2).PadLeft(sizeof(UInt32) * 8, '0');
// Convert the input string to a character array
char[] charArray = inputString.ToCharArray();
// Start at the back of the binary string
int startPosition = numberBinary.Length;
// Create a string builder to construct the XOR string
StringBuilder stringBuilder = new();
for (int i = 0; i < charArray.Length; i++)
{
// Get the current character as a numeric value
int charCode = charArray[i] & 0xff;
// Determine where to start the string copy operation
startPosition = startPosition == 0 ? numberBinary.Length - 8 : startPosition - 8;
// Copy the binary digits as a string of the byte we are interested in
string byteString = numberBinary.Substring(startPosition, 8);
// Convert the binary string to a numeric value
int byteNumber = Convert.ToInt32(byteString, 2);
// Append the XOR between the character code and byte
stringBuilder.Append((charCode ^ byteNumber).ToString("X2"));
}
return stringBuilder.ToString();
}
// Function to encrypt a string
private string Encrypt(string parameters)
{
// Create an AES128 object
using Aes aes128 = Aes.Create();
// Set the AES key to our session key we calculated
aes128.Key = SessionAesKey;
// Generate a random initialisation vector
aes128.GenerateIV();
// Convert the parameter string into UTF8 bytes
byte[] parameterBytes = Encoding.UTF8.GetBytes(parameters);
// Encrypt the bytes
byte[] encryptedBytes = aes128.EncryptCbc(parameterBytes, aes128.IV, PaddingMode.PKCS7);
// Convert the initialization vector to a lowercase hexadecimal string
string ivString = BitConverter.ToString(aes128.IV).Replace("-", "");
// Convert encrypted bytes to a lowercase hexadecimal string
string dataString = BitConverter.ToString(encryptedBytes).Replace("-", "");
// Return the concatenation of hexadecimal strings
return $"{ivString}{dataString}";
}
// Function to decrypt a string
private string Decrypt(string data)
{
// Create an AES128 object
using Aes aes128 = Aes.Create();
// Set the AES key to our session key we calculated
aes128.Key = SessionAesKey;
// Convert the input data to UTF8 bytes
byte[] dataBytes = Convert.FromHexString(data);
// Read the initialisation vector from the first 16 bytes
aes128.IV = dataBytes[..16];
// Decrypt the rest of the bytes
byte[] decryptedBytes = aes128.DecryptCbc(dataBytes[16..], aes128.IV, PaddingMode.PKCS7);
// Return the bytes converted to string
return Encoding.UTF8.GetString(decryptedBytes);
}
// Function to log in to as an operator
public async Task<bool> LogIn(string username, string passwordHash)
{
// Create a new session and retrieve the first random value
string sessionRandIdString = await GetResponseString("Command&Type=Session&SubType=InitSession");
// Convert the number from a string to a unsigned integer so we can do arithmetic with it
uint sessionRandIdValue = UInt32.Parse(sessionRandIdString);
// Calculate the XOR between the username and first random value plus one
string xorUsername = XorFn(username, sessionRandIdValue + 1);
// Calculate the SHA1 checksum of the username XOR
string hashXorUsername = await Sha1FromString(xorUsername);
// Calculate the XOR between the password SHA1 checksum and the first random value
string xorPasswordHash = XorFn(passwordHash, sessionRandIdValue);
// Calculate the SHA1 checksum of the password hash XOR
string hashXorPasswordHash = await Sha1FromString(xorPasswordHash);
// Determine which function to use for authentication
string checkPasswordFunction = HostHttps ? "CheckPasswordServer" : "CheckPassword";
// Retrieve the second random number
string sessionRandIdString2 = await GetResponseString($"Command&Type=Session&SubType={checkPasswordFunction}&Name={hashXorUsername}&Password={hashXorPasswordHash}");
// Check if the retrieval failed
if (sessionRandIdString2.Trim().StartsWith("FAIL"))
{
Console.WriteLine($"Error in authentication: {sessionRandIdString2}");
return false;
}
// For AES over HTTPS we can now generate the session key
if (HostHttps == false)
{
// Convert the second random value to a number
uint sessionRandIdValue2 = UInt32.Parse(sessionRandIdString2);
// Calculate the XOR between the SHA1 checksum of the password and the second random value
string xorPasswordHash2 = XorFn(passwordHash, sessionRandIdValue2);
// Calculate the SHA1 checksum of the password XOR
string hashXorPasswordHash2 = await Sha1FromString(xorPasswordHash2);
// The session key is first 16 bytes
SessionAesKey = Encoding.UTF8.GetBytes(hashXorPasswordHash2[..16]);
}
return true;
}
// Function to log out of the controller
public async Task LogOut()
{
await GetResponseString("Command&Type=Session&SubType=CloseSession");
}
// Function to retrieve the controller settings
public async Task<NameValueCollection> GetControllerSettings()
{
string list = await GetResponseString("Request&Type=Detail&SubType=GXT_CONTROLLERSETTINGS_TBL");
return HttpUtility.ParseQueryString(list);
}
}
}
Log in with HTTPS
Copy
ControllerAPI httpClient = new("192.168.1.2", true /* HTTPS on */);
string passwordHash = await ControllerAPI.Sha1FromString("soft");
Console.WriteLine("Logging in to controller (HTTPS)");
bool loggedIn = await httpClient.LogIn("admin", passwordHash.ToLower());
if (!loggedIn)
{
Console.WriteLine("Failed to log in (HTTPS)");
return;
}
Console.WriteLine("Retrieving the details of the controller (HTTPS)");
NameValueCollection details = await httpClient.GetControllerSettings();
string? serial = details["SERIALNUMBER"];
Console.WriteLine($"Controller's serial number is '{serial}' (HTTPS)");
await httpClient.LogOut();
Log in with HTTP
Copy
ControllerAPI httpClient = new("192.168.1.2", false /* HTTPS off */);
string passwordHash = await ControllerAPI.Sha1FromString("soft");
Console.WriteLine("Logging in to controller (HTTP)");
bool loggedIn = await httpClient.LogIn("admin", passwordHash.ToLower());
if (!loggedIn)
{
Console.WriteLine("Failed to log in");
return;
}
Console.WriteLine("Retrieving the details of the controller (HTTP)");
NameValueCollection details = await httpClient.GetControllerSettings();
string? serial = details["SERIALNUMBER"];
Console.WriteLine($"Controller's serial number is '{serial}' (HTTP)");
await httpClient.LogOut();